//
// Created by root on 4/29/17.
//

#include "../runner.h"

#define S_TRUE  "(define true (lex \"Boolean\" \"#t\"))"
#define S_FALSE  "(define false (lex \"Boolean\" \"#f\"))"

#define S_MAKE_INSTANCE   "(define (make-instance)\n\
  (let ((handler (lex \"Boolean\" \"#f\")))\n\
    (lambda (message)\n\
      (case message\n\
        ((SET-HANDLER!)\n\
         (lambda (handler-proc)\n\
           (set! handler handler-proc)))\n\
        (else (get-method message handler))))))"

#define S_CREATE_INSTANCE    "(define (create-instance maker . args)\n\
  (let* ((instance (make-instance))\n\
         (handler (apply maker instance args)))\n\
    (ask instance SET-HANDLER! handler)\n\
    instance))"

#define S_CREATE_ROOT_OBJECT      "(define (create-root-object)\n\
  (create-instance make-root-object))"

#define S_MAKE_ROOT_OBJECT        "(define (make-root-object self)\n\
  (lambda (message) (self message)))"

#define S_CREATE_NAMED_OBJECT      "(define (create-named-object name)\n\
  (create-instance make-named-object name))"

#define S_MAKE_NAMED_OBJECT        "(define (make-named-object self name)\n\
  (let ((root-part (make-root-object self)))\n\
    (lambda (message)\n\
      (case message\n\
        ((NAME) \n\
         (lambda () name))\n\
        ((CHANGE-NAME) \n\
         (lambda (newname) (set! name newname)))\n\
        (else (get-method message root-part))))))"

#define S_MAKE_NAMED_OBJECT_HANDLER      "(define (make-named-object-handler name)\n\
  (lambda (message)\n\
    (case message\n\
      ((NAME)\n\
       (lambda () name))\n\
      ((CHANGE-NAME)\n\
       (lambda (new-name) (set! name new-name)))\n\
      (else (no-method))) ))"

#define S_CREATE_BOOK     "(define (create-book name copyright)\n\
  (create-instance make-book name copyright))"

#define S_MAKE_BOOK     "(define (make-book self name copyright)\n\
  (let ((named-object-part (make-named-object self name)))\n\
    (lambda (message)\n\
      (case message\n\
        ((YEAR) (lambda () copyright))\n\
        (else (get-method message named-object-part))))))"

#define S_GET_METHOD     "(define (get-method message object)\n\
  (object message))"

#define S_ASK      "(define (ask object message . args)\n\
  (let ((method (get-method message object)))\n\
    (if (lex \"Boolean\" \"#t\")\n\
        (apply method args)\n\
        (error \"No method for message\" message))))"

#define DEF_1 "(define z-name (lex \"Symbol\" \"sicp\"))"

#define DEF_2 "(define z-year (lex \"Integer\" \"1996\"))"

#define DEF_Z     "(define z (create-book (lex \"Symbol\" \"sicp\") (lex \"Integer\" \"1996\")))"

#define ASK_NAME  "(ask z NAME)"

#define ASK_YEAR  "(ask z YEAR)"


/*
(define (make-instance)
  (let ((handler #f))
    (lambda (message)
      (case message
        ((SET-HANDLER!)
         (lambda (handler-proc)
           (set! handler handler-proc)))
        (else (get-method message handler))))))
;

(define (create-instance maker . args)
  (let* ((instance (make-instance))
         (handler (apply maker instance args)))
    (ask instance 'SET-HANDLER! handler)
    instance))
;


;

(define (make-named-object-handler name)
  (lambda (message)
    (cond ((eq? message 'NAME)
           (lambda () name))
          ((eq? message 'CHANGE-NAME)
           (lambda (new-name) (set! name new-name)))
          (else (no-method)))))
;

(define (make-named-object-handler name)
  (lambda (message)
    (case message
      ((NAME)
       (lambda () name))
      ((CHANGE-NAME)
       (lambda (new-name) (set! name new-name)))
      (else (no-method))) ))
;

(define (create-book name copyright)
  (create-instance make-book name copyright))
;

(define (make-book self name copyright)
  (let ((named-object-part (make-named-object self name)))
    (lambda (message)
      (case message
        ((TYPE) (lambda ()
                  (type-extend 'book named-object)))
        ((YEAR) (lambda () copyright))
        (else (get-method message named-object-part))))))
;

(define (get-method message object)
  (object message))
;


(define (ask object message . args)
  (let ((method (get-method message object)))
    (if (method? method)
        (apply method args)
        (error "No method for message" message))))
;

(define z (create-book ’sicp 1996))

(ask z 'NAME)
 * */

void test(){
    //eval(global_scheme, S_TRUE);
    //print_result();
    //eval(global_scheme, S_FALSE);
    //print_result();
    eval(global_scheme, S_MAKE_INSTANCE);
    print_result();
    eval(global_scheme, S_CREATE_INSTANCE);
    print_result();
    eval(global_scheme, S_CREATE_ROOT_OBJECT);
    print_result();
    eval(global_scheme, S_MAKE_ROOT_OBJECT);
    print_result();
    eval(global_scheme, S_CREATE_NAMED_OBJECT);
    print_result();
    eval(global_scheme, S_MAKE_NAMED_OBJECT);
    print_result();
    //eval(global_scheme, S_MAKE_NAMED_OBJECT_HANDLER);
    //print_result();
    eval(global_scheme, S_CREATE_BOOK);
    print_result();
    eval(global_scheme, S_MAKE_BOOK);
    print_result();
    eval(global_scheme, S_GET_METHOD);
    print_result();
    eval(global_scheme, S_ASK);
    print_result();
    //eval(global_scheme, DEF_1);
    //print_result();
    //eval(global_scheme, DEF_2);
    //print_result();
    eval(global_scheme, DEF_Z);
    print_result();
    eval(global_scheme, ASK_NAME);
    print_result();
    eval(global_scheme, ASK_YEAR);
    print_result();

    write_log2("test_class", "test", "===============================");

    eval(global_scheme, "(define abc (create-book (lex \"Symbol\" \"mybook\") (lex \"Integer\" \"2017\")))");
    print_result();
    eval(global_scheme, "(ask abc NAME)");
    print_result();
    eval(global_scheme, "(set! abc (create-book (lex \"Symbol\" \"mybook\") (lex \"Integer\" \"2017\")))");
    print_result();
    eval(global_scheme, "(ask abc YEAR)");
    print_result();

    eval(global_scheme, "(define def (create-book (lex \"Symbol\" \"BOOK_DEF\") (lex \"Integer\" \"8888\")))");
    print_result();
    eval(global_scheme, "(ask def NAME)");
    print_result();
    eval(global_scheme, "(ask def YEAR)");
    print_result();
}

void print_result() {
    printf("%s\n", cell2str(global_scheme, global_scheme->returnValue));
}

//TODO
int main(int argc, char **argv) {
    const struct CMUnitTest tests[] = {
            cmocka_unit_test(test),
    };
    return cmocka_run_group_tests(tests, setup_scheme, teardown_scheme);
}
